﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Engine.Dtd.Schema;
using System.Xml;
using System.Diagnostics;

namespace Engine.Sgml.Parser {
    internal sealed class ParsedCDataReader : IElementReader {
        private StringBuilder builder;

        public ParsedCDataReader(StringBuilder builder) {
            Guard.ArgumentNotNull(builder, "builder");
            this.builder = builder;
            }

        public SElement Read(SgmlStreamReader reader, SElement parent, Context context) {
            builder.Clear();
            SElement node = parent.LastNode;
            if (node != null && node.NodeType == SgmlNodeType.WhiteSpace) {
                builder.Append(node.Value);
                parent.Nodes.Pop();
                }
            if (parent.HasDeclaration && parent.Declaration.ContentModel.TextOnly)
                return ReadTextOnly(reader, parent, context);
            else
                return ReadText(reader, parent, context);
            }

        private SElement ReadTextOnly(SgmlStreamReader reader, SElement parent, Context context) {
            do {
                switch (reader.CurrentChar) {
                    case Characters.EndOfFile:
                        context.TraceSource.TraceEvent(TraceEventType.Error, 0, "The PCDATA was not closed {0}.", parent.Context());
                        SElement text = CreateTextElement(parent, context);
                        text.DName = DName.PCData;
                        text.NodeType = SgmlNodeType.Text;
                        text.Value = builder.ToString();
                        builder.Clear();
                        return text;
                    case Characters.StartTagMarkup:
                        char next = reader.PeekChar();
                        if (next == Characters.ClosingTagMarkup) {
                            // Consume '<' and '/'  symbols
                            reader.ReadChar();
                            reader.ReadChar();
                            string elementName = context.NameReader.ReadPrefixNameToken(reader);
                            if (elementName != null && reader.SkipWhitespace() == Characters.EndTagMarkup && parent.Equals(elementName)) {
                                reader.ReadChar();
                                text = CreateTextElement(parent, context);
                                SetValue(text, builder);
                                builder.Clear();

                                // Create and return end element
                                SElement closing = parent.Nodes.Push();
                                closing.Clear();
                                closing.Parent = parent;
                                closing.Name = parent.Name;
                                closing.DName = parent.DName;
                                closing.Declaration = parent.Declaration;
                                closing.NodeType = SgmlNodeType.EndElement;
                                return closing;
                                }
                            else {
                                builder.Append(Characters.StartTagMarkup);
                                builder.Append(Characters.ClosingTagMarkup);
                                builder.Append(elementName);
                                continue;
                                }
                            }
                        builder.Append(reader.CurrentChar);
                        reader.ReadChar();
                        break;
                    case Characters.StartEntityReference:
                        context.EntityResolver.Resolve(reader, builder);
                        break;
                    default:
                        builder.Append(reader.CurrentChar);
                        reader.ReadChar();
                        break;
                    }
                } while (true);
            }

        private SElement ReadText(SgmlStreamReader reader, SElement parent, Context context) {
            do {
                switch (reader.CurrentChar) {
                    case Characters.EndOfFile:
                        context.TraceSource.TraceEvent(TraceEventType.Error, 0, "The PCDATA was not closed {0}.", parent.Context());
                        SElement text = CreateTextElement(parent, context);
                        text.Value = builder.ToString();
                        builder.Clear();
                        return text;
                    case Characters.StartTagMarkup:
                        char next = reader.PeekChar();
                        if (next == Characters.ClosingTagMarkup || XmlConvert.IsStartNCNameChar(next)) {
                            text = CreateTextElement(parent, context);
                            SetValue(text, builder);
                            builder.Clear();
                            return text;
                            }
                        builder.Append(reader.CurrentChar);
                        reader.ReadChar();
                        break;
                    case Characters.StartEntityReference:
                        context.EntityResolver.Resolve(reader, builder);
                        break;
                    default:
                        builder.Append(reader.CurrentChar);
                        reader.ReadChar();
                        break;
                    }
                } while (true);
            }

        private static SElement CreateTextElement(SElement parent, Context context) {
            SElement text = parent.Nodes.Push();
            text.Clear();
            text.Parent = parent;
            text.LineNumber = context.LineNumber;
            text.LinePosition = context.LinePosition;
            text.DName = DName.PCData;
            text.NodeType = SgmlNodeType.Text;
            return text;
            }

        private static void SetValue(SElement text, StringBuilder builder) {
            text.Value = builder.ToString();
            }
        }
    }
